package com.jdk.demo.unsafe;

import java.lang.reflect.Field;
import java.util.HashMap;
import sun.misc.Unsafe;
//import org.openjdk.jol.info.ClassLayout;
//import org.openjdk.jol.vm.VM;

public class UnsafeTest {

	public static void main(String[] args) throws Exception {
		//System.out.println(VM.current().details());
		//System.out.println(ClassLayout.parseClass(VO.class).toPrintable());
		//System.out.println("=================");
		Unsafe unsafe = getUnsafeInstance();
		VO vo = new VO();
		vo.a = 2;
		vo.b = 3;
		vo.d = new HashMap<>();
		// aoffset=unsafe.objectFieldOffset(field) 从方法名上可以这样理解：获取object对象的属性Field的偏移量
		long aoffset = unsafe.objectFieldOffset(VO.class.getDeclaredField("a"));
		System.out.println("aoffset=" + aoffset);
		System.out.println("boffset=" + unsafe.objectFieldOffset(VO.class.getDeclaredField("b")));
		System.out.println("coffset=" + unsafe.objectFieldOffset(VO.class.getDeclaredField("c")));
		System.out.println("doffset=" + unsafe.objectFieldOffset(VO.class.getDeclaredField("d")));
		System.out.println("eoffset=" + unsafe.objectFieldOffset(VO.class.getDeclaredField("e")));
		System.out.println("hoffset=" + unsafe.objectFieldOffset(VO.class.getDeclaredField("h")));
		System.out.println("ioffset=" + unsafe.objectFieldOffset(VO.class.getDeclaredField("i")));
		// 获取a的值
		// VO vo=new VO();的时候，Java内存中就开辟了一块地址，包含一个固定长度的对象头（假设是16字节，不同位数机器/对象头是否压缩都会影响对象头长度）+实例数据（4字节的a+4字节的b）+padding。
		// 这里直接说结论，我们上面说的偏移量就是在这里体现，如上面a属性的偏移量就是16，b属性的偏移量就是20。
		// 在unsafe类里面，我们发现一个方法unsafe.getInt(object, offset);
		// 通过unsafe.getInt(vo, 16) 就可以得到vo.a的值。是不是联想到反射了？其实java的反射底层就是用的UNSAFE
		int va = unsafe.getInt(vo, aoffset);
		System.out.println("va=" + va);
	}

	public static Unsafe getUnsafeInstance() throws Exception {
		// 通过反射获取rt.jar下的Unsafe类
		Field theUnsafeInstance = Unsafe.class.getDeclaredField("theUnsafe");
		theUnsafeInstance.setAccessible(true);
		// return (Unsafe) theUnsafeInstance.get(null);是等价的
		return (Unsafe) theUnsafeInstance.get(Unsafe.class);
	}

	/**
	 * 在我本地机器测试结果如下：
		# Running 64-bit HotSpot VM.
		# Using compressed oop with 0-bit shift.
		# Using compressed klass with 3-bit shift.
		# Objects are 8 bytes aligned.
		# Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
		# Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]

		com.ha.net.nsp.product.VO object internals:
		 OFFSET  SIZE               TYPE DESCRIPTION                               VALUE
		      0    12                    (object header)                           N/A
		     12     4                int VO.a                                      N/A
		     16     8               long VO.b                                      N/A
		     24     4                int VO.e                                      N/A
		     28     1            boolean VO.i                                      N/A
		     29     3                    (alignment/padding gap)
		     32     4   java.lang.String VO.c                                      N/A
		     36     4   java.lang.Object VO.d                                      N/A
		     40     4   java.lang.Object VO.h                                      N/A
		     44     4                    (loss due to the next object alignment)
		Instance size: 48 bytes
		Space losses: 3 bytes internal + 4 bytes external = 7 bytes total

		=================
		aoffset=12
		va=2

		在结果中，我们发现：
		1、我本地的虚拟机环境是64位并且开启了compressed压缩，对象都是8字节对齐
		2、VO类的内存布局包含12字节的对象头，4字节的int数据，8字节的long数据，其他String和Object是4字节，最后还有4字节的对齐。
		3、VO类属性的内存布局跟属性声明的顺序不一致。
		4、VO类的static属性不在VO的内存布局中，因为他是属于class类。
		5、通过VO类就可以确定一个对象占用的字节数，这个占用空间在编译阶段就已经确定（注：此占用空间并不是对象的真实占用空间，）。
		6、如上，通过偏移量12就可以读取到此处存放的值是2。
		引申出新的问题：
		1、这里的对象头为什么是12字节？对象头里都具体包含什么？
		答：正常情况下，对象头在32位系统内占用一个机器码也就是8个字节，64位系统也是占用一个机器码16个字节。但是在我本地环境是开启了reference（指针）压缩，所以只有12个字节。
		2、这里的String和Object为什么都是4字节？
		答：因为String或者Object类型，在内存布局中，都是reference类型，所以他的大小跟是否启动压缩有关。未启动压缩的时候，32位机器的reference类型是4个字节，64位是8个字节，但是如果启动压缩后，64位机器的reference类型就变成4字节。
		3、Java怎么知道应该从偏移量12读取到偏移量16呢，而不是读取到偏移量18或者20？
		答：这里我猜测，虚拟机在编译阶段，就已经保留了一个VO类的偏移量数组，那12后的偏移量就是16，所以Java知道读到16为止。
	 * */

}
